(function (global) {
  if (global.report) return;
  var report = [],
    reported = {},
    config = {},
    logs = [],
    timeout = null,
    isOBJByType = function (o, type) {
      return (
        Object.prototype.toString.call(o) ===
        "[object " + (type || "Object") + "]"
      );
    },
    isOBJ = function (obj) {
      var type = typeof obj;
      return type === "object" && !!obj;
    },
    isEmpty = function (obj) {
      if (obj === null) return true;
      if (isOBJByType(obj, "Number")) {
        return false;
      }
      return !obj;
    },
    hasOwn = {}.hasOwnProperty,
    isPlainObject = function (obj) {
      var key;
      if (!obj || typeof obj !== "object" || obj.nodeType) {
        return false;
      }
      try {
        if (
          obj.constructor &&
          !hasOwn.call(obj, "constructor") &&
          !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")
        ) {
          return false;
        }
      } catch (e) {
        return false;
      }
      for (key in obj) {
        continue;
      }
      return key === undefined || hasOwn.call(obj, key);
    },
    reportToString = function (report, index) {
      return new Promise((resolve, reject) => {
        try {
          var stringify = [];
          var param = [];
          var params = [];
          for (var i = 0; i < report.length; i++) {
            var error = report[i];
            if (isOBJ(error)) {
              error.level = error.level || config.level;
              for (var key in error) {
                var value = error[key];
                if (!isEmpty(value)) {
                  if (isOBJ(value)) {
                    try {
                      value = JSON.stringify(value);
                    } catch (err) {
                      value =
                        "[BJ_REPORT detect value stringify error] " +
                        err.toString();
                    }
                  }
                  stringify.push(key + ":" + value);
                  param.push(key + "=" + encodeURIComponent(value));
                  params.push(
                    key + "[" + index + "]=" + encodeURIComponent(value)
                  );
                }
              }
            }
          }
          resolve([(params.join("&"), stringify.join(","), param.join("&"))]);
        } catch (err) {
          reject(err);
        }
      });
    },
    processStackMsg = function (error) {
      var stack = error.stack
        .replace(/\n/gi, "")
        .split(/\bat\b/)
        .slice(0, 9)
        .join("@")
        .replace(/\?[^:]+/gi, "");
      var msg = isPlainObject(error) ? JSON.stringify(error) : error.toString();
      if (stack.indexOf(msg) < 0) {
        stack = msg + "@" + stack;
      }
      return stack;
    },
    submitLog = function () {
      var api =
        config.url +
        "?id=" +
        config.id +
        "&d=" +
        encodeURIComponent(logs.join("&")) +
        "&c=" +
        logs.length +
        "&t=" +
        new Date().getTime();
      var _img = document.createElement("img"),
        head = document.querySelector("body");
      head.appendChild(_img);
      _img.src = api;
      _img.onerror = _img.onload = function () {
        head.removeChild(_img);
      };
      report = [];
      logs = [];
    },
    processLog = function (isReportNow) {
      config.ext && logs.push(config.ext);
      report = report.filter((item) => {
        let isIn = false,
          isId = null;
        for (let n in reported) {
          if (item.msg === reported[n].error.msg) {
            isIn = true;
            isId = n;
            break;
          }
        }
        if (!isIn) {
          reported[new Date().getTime()] = { error: item, count: 1 };
          return true;
        } else {
          if (reported[isId].count + 1 > config.report) {
            return false;
          } else {
            reported[isId].count += 1;
            return true;
          }
        }
      });
      report.length > 0 &&
        reportToString(report, report.length)
          .then((res) => {
            logs = [...res];
            if (logs.length > 0)
              isReportNow
                ? submitLog()
                : (timeout && clearTimeout(timeout),
                  (timeout = setTimeout(submitLog, config.delay * 1000)));
          })
          .catch((err) => console.log(err));
    },
    errorFun = function (msg, url, line, col, error) {
      var newMsg = msg;

      if (error && error.stack) {
        newMsg = processStackMsg(error);
      }

      if (isOBJByType(newMsg, "Event")) {
        newMsg += newMsg.type
          ? "--" +
            newMsg.type +
            "--" +
            (newMsg.target
              ? newMsg.target.tagName + "::" + newMsg.target.src
              : "")
          : "";
      }

      report.push({
        msg: newMsg,
        target: url,
        rowNum: line,
        colNum: col,
        _orgMsg: msg,
      });
      report = Array.from(new Set(report));
      processLog(config.delay === 0 ? true : false);
    };

  global.report = function (ops) {
    ops = ops || {};
    config = {
      ...config,
      id: 0, //上报id
      url: "", //上报接口url
      ext: {}, //上报扩展参数
      level: 4, //上报等级，1-debug，2-info，4-error,
      ignore: [], //上报忽略的内容类型错误
      random: 0, //上报抽样随机数
      delay: 1000, //延迟上报间隔时间
      repeat: 5, //同一个问题上报次数超过此值，不再上报
      ...ops,
    };
    return {
      addError(err) {
        var newMsg = err.msg;
        if (err && err.stack) {
          newMsg = processStackMsg(err);
        }

        if (isOBJByType(newMsg, "Event")) {
          newMsg += newMsg.type
            ? "--" +
              newMsg.type +
              "--" +
              (newMsg.target
                ? newMsg.target.tagName + "::" + newMsg.target.src
                : "")
            : "";
        }

        report.push({
          msg: newMsg,
          target: err.url,
          rowNum: err.line,
          colNum: err.col,
          _orgMsg: err.msg,
        });
        report = Array.from(new Set(report));
        processLog(config.delay === 0 ? true : false);
      },
    };
  };

  var orgError = global.onerror;
  var onUnhandledrejection = global.onunhandledrejection;
  global.onunhandledrejection = function (event) {
    errorFun.call(global, event.reason);
    onUnhandledrejection && onUnhandledrejection.apply(global, arguments);
  };
  global.onerror = function () {
    errorFun.apply(global, arguments);
    orgError && orgError.apply(global, arguments);
  };
})(window);
